package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"go-web/model"
	"io"
	"log"
	"math/rand"
	"os"
	"strconv"
	"time"
)

var db *gorm.DB
var db1 *gorm.DB


func initDB(user string, password string, host string, database string) *gorm.DB {
	conn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s",
		user,
		password,
		host,
		database,
	)

	var dbNew *gorm.DB
	var err error
	if dbNew, err = gorm.Open("mysql", conn); err != nil {
		log.Fatalf("Fail to connect db : %v", err)
	}

	//defer DB.Close()

	dbNew.LogMode(false)
	//默认加s， grom转义struct名字的时候不用加上s
	//db.SingularTable(true)

	// idle 空闲的
	dbNew.DB().SetMaxIdleConns(40)
	dbNew.DB().SetMaxOpenConns(40)

	return dbNew
}

func main() {

	db = initDB(
		"root",
		"",
		"127.0.0.1:3306",
		"cms",
	)

	//db = initDB(
	//	"admin123",
	//	"Lingmaimai001",
	//	"pc-bp1vz6jcr4d78l8b5.mysql.polardb.rds.aliyuncs.com:3306",
	//	"mall",
	//)

	//db1 = initDB(
	//	"admin123",
	//	"Lingmaimai001",
	//	"pc-bp1vz6jcr4d78l8b5.mysql.polardb.rds.aliyuncs.com:3306",
	//	"mall",
	//)


	rand.Seed(time.Now().UnixNano())

	gin.SetMode(gin.ReleaseMode)
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)

	r := gin.Default()

	r.GET("/select1", func(c *gin.Context) {
		var goods model.Goods
		db.Find( &goods, 1)

		c.JSON(200, gin.H{
			"goods": goods,
		})
	})

	r.GET("/select2", func(c *gin.Context) {
		var goods model.Goods
		db.Find( &goods, 1)

		var order model.Order
		db.Find( &order, 1)

		c.JSON(200, gin.H{
			"goods": goods,
			"order": order,
		})
	})

	r.GET("/selectById", func(c *gin.Context) {
		var goods model.Goods
		db.Find( &goods, rand.Intn(300000))

		c.JSON(200, gin.H{
			"goods": goods,
		})
	})


	// innodb行锁测试
	r.GET("/decr", func(c *gin.Context) {
		decr()
		c.JSON(200, gin.H{
			"decr": 0,
		})
	})

	// innodb随机行锁测试
	r.GET("/decrRand", func(c *gin.Context) {
		decrRand()
		c.JSON(200, gin.H{
			"decrRand": 0,
		})
	})

	// innodb事务+行锁测试
	r.GET("/txAndDecr", func(c *gin.Context) {
		txAndDecr()
		c.JSON(200, gin.H{
			"txAndDecr": 0,
		})
	})


	// innodb事务+随机行锁测试
	r.GET("/txAndRandDecr", func(c *gin.Context) {
		txAndRandDecr()
		c.JSON(200, gin.H{
			"txAndRandDecr": 0,
		})
	})

	// 插入
	r.GET("/insert", func(c *gin.Context) {
		insert()
		c.JSON(200, gin.H{
			"insert": 0,
		})
	})

	// 插入 + update
	r.GET("/insertAndUpdate", func(c *gin.Context) {
		insertAndUpdate()
		c.JSON(200, gin.H{
			"insertAndUpdate": 0,
		})
	})

	// innodb事务+插入
	r.GET("/txAndInsert", func(c *gin.Context) {
		txAndInsert()
		c.JSON(200, gin.H{
			"txAndInsert": 0,
		})
	})


	// 下单无事务
	r.GET("/decrAndInsert", func(c *gin.Context) {
		decrAndInsert()
		c.JSON(200, gin.H{
			"txAndDecr": 0,
		})
	})
	// 下单有事务
	r.GET("/txAndDecrAndInsert", func(c *gin.Context) {
		txAndDecrAndInsert()
		c.JSON(200, gin.H{
			"txAndDecr": 0,
		})
	})

	// 分表插入
	r.GET("/insertMulti", func(c *gin.Context) {
		number, _ := strconv.Atoi(c.DefaultQuery("number", "1"))
		insertMulti(number)
		c.JSON(200, gin.H{
			"insertMulti": 0,
		})
	})

	// innodb 双主机插入
	r.GET("/insert2Host", func(c *gin.Context) {
		insert2Host()
		c.JSON(200, gin.H{
			"insert2Host": 0,
		})
	})

	// 空事务
	r.GET("/txEmpty", func(c *gin.Context) {
		tx := db.Begin()
		tx.Commit()
		c.JSON(200, gin.H{
			"tx": 1,
		})
	})

	r.GET("/nosql", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"nosql": 1,
		})
	})


	r.Run()
}

func decr() {
	var goods model.Goods
	rowsAffected := db.Model(&goods).Where("id = ?", 1).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
	}
}

func decrRand() {
	id := rand.Intn(100000)
	var goods model.Goods
	rowsAffected := db.Model(&goods).Where("id = ?", id).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
	}
}

func txAndDecr() {
	tx := db.Begin()
	var goods model.Goods
	rowsAffected := tx.Model(&goods).Where("id = ?", 1).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
		tx.Rollback()
		return
	}
	tx.Commit()
}

func txAndRandDecr() {
	goodsId := rand.Intn(100000)
	tx := db.Begin()
	var goods model.Goods
	rowsAffected := tx.Model(&goods).Where("id = ?", goodsId).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
		tx.Rollback()
		return
	}
	tx.Commit()
}

func insert() {
	var order = new(model.Order)
	order.UserId = rand.Intn(1000000)
	order.GoodsId = rand.Intn(1000000)
	order.Number = 1
	if err := db.Create(order).Error; err != nil {
		return
	}
}

func insertAndUpdate() {
	var order = new(model.Order)
	order.UserId = rand.Intn(1000000)
	order.GoodsId = rand.Intn(1000000)
	order.Number = 1
	if err := db.Create(order).Error; err != nil {
		return
	}

	order.Number = 999
	db.Save(order)
}


func txAndInsert() {
	userId := rand.Intn(1000000)
	goodsId := rand.Intn(380000)
	tx := db.Begin()
	var order = new(model.Order)
	order.UserId = userId
	order.GoodsId = goodsId
	order.Number = 1
	if err := tx.Create(order).Error; err != nil {
		tx.Rollback()
		return
	}
	tx.Commit()
}

func insertMulti(orderTableCount int) {
	var order = new(model.Order)
	order.UserId = rand.Intn(1000000)
	order.GoodsId = rand.Intn(1000000)
	order.Number = 1
	tableIndex := order.UserId % orderTableCount + 1
	tableName := fmt.Sprintf("orders_%d", tableIndex)
	if err := db.Table(tableName).Create(order).Error; err != nil {
		return
	}
}



func decrAndInsert() {
	goodsId := 1
	userId := rand.Intn(100000)
	var goods model.Goods
	rowsAffected := db.Model(&goods).Where("id = ?", goodsId).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
		return
	}

	var order = new(model.Order)
	order.UserId = userId
	order.GoodsId = userId
	order.Number = 1
	if err := db.Create(order).Error; err != nil {
		return
	}

}

func txAndDecrAndInsert() {
	goodsId := 1
	userId := rand.Intn(100000)
	tx := db.Begin()
	var goods model.Goods
	rowsAffected := tx.Model(&goods).Where("id = ?", goodsId).Where("stock - ? >= 0", 1).UpdateColumn("stock", gorm.Expr("stock - ?", 1)).RowsAffected
	if rowsAffected == 0 {
		tx.Rollback()
		return
	}

	var order = new(model.Order)
	order.UserId = userId
	order.GoodsId = userId
	order.Number = 1
	if err := tx.Create(order).Error; err != nil {
		tx.Rollback()
		return
	}
	tx.Commit()
}

func insert2Host() {
	var order = new(model.Order)
	order.UserId = rand.Intn(10000000)
	order.GoodsId = rand.Intn(10000000)
	order.Number = 1

	var dbRand *gorm.DB
	i := rand.Intn(2)
	if i == 0 {
		dbRand = db
	} else {
		dbRand = db1
	}

	tableIndex := order.UserId / 10  % 16 + 1
	tableName := fmt.Sprintf("orders_%d", tableIndex)

	if err := dbRand.Table(tableName).Create(order).Error; err != nil {
		return
	}
}

